/*
 *  Workspace window manager
 *  Copyright (c) 2015-2021 Sergii Stoian
 *
 *  WINGs library (Window Maker)
 *  Copyright (c) 1997-2003 Alfredo K. Kojima
 *  Copyright (c) 1997-2003 Alfredo K. Kojima
 *  Copyright (c) 2001 Dan Pascu
 *  Copyright (c) 2014 Window Maker Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

#include <string.h>

#include "WM.h"

#include "WMcore.h"
#include "util.h"
#include "whashtable.h"
#include "string_utils.h"

#include "wscreen.h"
#include "widgets.h"
#include "wevent.h"
#include "wcolor.h"
#include "drawing.h"

#ifdef USE_XSHAPE
#include <X11/extensions/shape.h>
#endif

typedef struct WMBalloon {
  WMView *view;

  WMHashTable *table;	/* Table from view ptr to text */

  WMColor *backColor;
  WMColor *textColor;
  WMFont *font;

  CFRunLoopTimerRef timer;	/* timer for showing balloon */

  CFRunLoopTimerRef noDelayTimer;

  int delay;

  Window forWindow;	/* window for which the balloon
                         *is being show in the moment */

  struct {
    WMAlignment alignment:2;
    unsigned enabled:1;

    unsigned noDelay:1;
  } flags;
} Balloon;

#define DEFAULT_WIDTH		60
#define DEFAULT_HEIGHT		14
#define DEFAULT_ALIGNMENT	WALeft
#define DEFAULT_DELAY		500

#define NO_DELAY_DELAY		150

static void destroyBalloon(Balloon *bPtr);

static void handleEvents(XEvent *event, void *data);

static void showText(Balloon *bPtr, int x, int y, int w, int h, const char *text);

struct WMBalloon *WMCreateBalloon(WMScreen *scr)
{
  Balloon *bPtr;

  bPtr = wmalloc(sizeof(Balloon));

  bPtr->view = WMCreateUnmanagedTopView(scr);
  if (!bPtr->view) {
    wfree(bPtr);
    return NULL;
  }
  bPtr->view->self = bPtr;

  bPtr->textColor = WMRetainColor(bPtr->view->screen->black);

  WMCreateEventHandler(bPtr->view, StructureNotifyMask, handleEvents, bPtr);

  WMResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
  bPtr->flags.alignment = DEFAULT_ALIGNMENT;

  bPtr->table = WMCreateHashTable(WMIntHashCallbacks);

  bPtr->delay = DEFAULT_DELAY;

  bPtr->flags.enabled = 1;

  return bPtr;
}

void WMSetBalloonTextAlignment(WMScreen *scr, WMAlignment alignment)
{
  scr->balloon->flags.alignment = alignment;

}

void WMSetBalloonTextForView(const char *text, WMView *view)
{
  char *oldText = NULL;
  WMScreen *scr = view->screen;

  if (text) {
    oldText = WMHashInsert(scr->balloon->table, view, wstrdup(text));
  } else {
    oldText = WMHashGet(scr->balloon->table, view);

    WMHashRemove(scr->balloon->table, view);
  }

  if (oldText) {
    wfree(oldText);
  }
}

void WMSetBalloonFont(WMScreen *scr, WMFont *font)
{
  Balloon *bPtr = scr->balloon;

  if (bPtr->font != NULL)
    WMReleaseFont(bPtr->font);

  if (font)
    bPtr->font = WMRetainFont(font);
  else
    bPtr->font = NULL;
}

void WMSetBalloonTextColor(WMScreen *scr, WMColor *color)
{
  Balloon *bPtr = scr->balloon;

  if (bPtr->textColor)
    WMReleaseColor(bPtr->textColor);

  bPtr->textColor = WMRetainColor(color);
}

void WMSetBalloonDelay(WMScreen *scr, int delay)
{
  scr->balloon->delay = delay;
}

void WMSetBalloonEnabled(WMScreen *scr, Bool flag)
{
  scr->balloon->flags.enabled = ((flag == 0) ? 0 : 1);

  WMUnmapView(scr->balloon->view);
}

static void clearNoDelay(CFRunLoopTimerRef timer, void *data)
{
  Balloon *bPtr = (Balloon *) data;

  bPtr->flags.noDelay = 0;
  bPtr->noDelayTimer = NULL;
}

void WMBalloonHandleLeaveView(WMView *view)
{
  Balloon *bPtr = view->screen->balloon;

  if (bPtr->forWindow == view->window) {
    if (bPtr->view->flags.mapped) {
      WMUnmapView(bPtr->view);
      bPtr->noDelayTimer = WMAddTimerHandler(NO_DELAY_DELAY, 0, clearNoDelay, bPtr);
    }
    if (bPtr->timer)
      WMDeleteTimerHandler(bPtr->timer);

    bPtr->timer = NULL;

    bPtr->forWindow = None;
  }
}

/*
 *botar balao perto do cursor
 *so mapear balao se o mouse ficar parado pelo delay
 *
 */

static void showBalloon(CFRunLoopTimerRef timer, void *data)
{
  char *text;
  WMView *view = (WMView *) data;
  Balloon *bPtr = view->screen->balloon;
  int x, y;
  Window foo;

  bPtr->timer = NULL;

  text = WMHashGet(bPtr->table, view);
  if (!text)
    return;

  XTranslateCoordinates(view->screen->display, view->window, view->screen->rootWin, 0, 0, &x, &y, &foo);

  if (!bPtr->view->flags.realized)
    WMRealizeView(bPtr->view);

  showText(bPtr, x, y, view->size.width, view->size.height, text);

  bPtr->flags.noDelay = 1;
}

void WMBalloonHandleEnterView(WMView *view)
{
  Balloon *bPtr = view->screen->balloon;
  char *text;

  if (!bPtr->flags.enabled)
    return;

  text = WMHashGet(bPtr->table, view);
  if (!text) {
    if (bPtr->view->flags.realized)
      WMUnmapView(bPtr->view);

    return;
  }

  if (bPtr->timer)
    WMDeleteTimerHandler(bPtr->timer);
  bPtr->timer = NULL;

  if (bPtr->noDelayTimer)
    WMDeleteTimerHandler(bPtr->noDelayTimer);
  bPtr->noDelayTimer = NULL;

  bPtr->forWindow = view->window;

  if (bPtr->flags.noDelay) {
    bPtr->timer = NULL;

    showBalloon(NULL, view);
  } else {
    bPtr->timer = WMAddTimerHandler(bPtr->delay, 0, showBalloon, view);
  }
}

#define TOP	0
#define BOTTOM	1
#define LEFT	0
#define RIGHT	2

#define 	SPACE	12

static void drawBalloon(WMScreen *scr, Pixmap bitmap, Pixmap pix, int x, int y, int w, int h, int side)
{
  Display *dpy = scr->display;
  WMColor *white = WMWhiteColor(scr);
  WMColor *black = WMBlackColor(scr);
  GC bgc = scr->monoGC;
  GC gc = WMColorGC(white);
  int rad = h *3 / 10;
  XPoint pt[3], ipt[3];
  int w1;

  /* outline */
  XSetForeground(dpy, bgc, 1);

  XFillArc(dpy, bitmap, bgc, x, y, rad, rad, 90 *64, 90 *64);
  XFillArc(dpy, bitmap, bgc, x, y + h - 1 - rad, rad, rad, 180 *64, 90 *64);

  XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y, rad, rad, 0 *64, 90 *64);
  XFillArc(dpy, bitmap, bgc, x + w - 1 - rad, y + h - 1 - rad, rad, rad, 270 *64, 90 *64);

  XFillRectangle(dpy, bitmap, bgc, x, y + rad / 2, w, h - rad);
  XFillRectangle(dpy, bitmap, bgc, x + rad / 2, y, w - rad, h);

  /* interior */
  XFillArc(dpy, pix, gc, x + 1, y + 1, rad, rad, 90 *64, 90 *64);
  XFillArc(dpy, pix, gc, x + 1, y + h - 2 - rad, rad, rad, 180 *64, 90 *64);

  XFillArc(dpy, pix, gc, x + w - 2 - rad, y + 1, rad, rad, 0 *64, 90 *64);
  XFillArc(dpy, pix, gc, x + w - 2 - rad, y + h - 2 - rad, rad, rad, 270 *64, 90 *64);

  XFillRectangle(dpy, pix, gc, x + 1, y + 1 + rad / 2, w - 2, h - 2 - rad);
  XFillRectangle(dpy, pix, gc, x + 1 + rad / 2, y + 1, w - 2 - rad, h - 2);

  if (side & BOTTOM) {
    pt[0].y = y + h - 1;
    pt[1].y = y + h - 1 + SPACE;
    pt[2].y = y + h - 1;
    ipt[0].y = pt[0].y - 1;
    ipt[1].y = pt[1].y - 1;
    ipt[2].y = pt[2].y - 1;
  } else {
    pt[0].y = y;
    pt[1].y = y - SPACE;
    pt[2].y = y;
    ipt[0].y = pt[0].y + 1;
    ipt[1].y = pt[1].y + 1;
    ipt[2].y = pt[2].y + 1;
  }

  /*w1 = WMAX(h, 24); */
  w1 = WMAX(h, 21);

  if (side & RIGHT) {
    pt[0].x = x + w - w1 + 2 *w1 / 16;
    pt[1].x = x + w - w1 + 11 *w1 / 16;
    pt[2].x = x + w - w1 + 7 *w1 / 16;
    ipt[0].x = x + 1 + w - w1 + 2 *(w1 - 1) / 16;
    ipt[1].x = x + 1 + w - w1 + 11 *(w1 - 1) / 16;
    ipt[2].x = x + 1 + w - w1 + 7 *(w1 - 1) / 16;
    /*ipt[0].x = pt[0].x+1;
      ipt[1].x = pt[1].x;
      ipt[2].x = pt[2].x; */
  } else {
    pt[0].x = x + w1 - 2 *w1 / 16;
    pt[1].x = x + w1 - 11 *w1 / 16;
    pt[2].x = x + w1 - 7 *w1 / 16;
    ipt[0].x = x - 1 + w1 - 2 *(w1 - 1) / 16;
    ipt[1].x = x - 1 + w1 - 11 *(w1 - 1) / 16;
    ipt[2].x = x - 1 + w1 - 7 *(w1 - 1) / 16;
    /*ipt[0].x = pt[0].x-1;
      ipt[1].x = pt[1].x;
      ipt[2].x = pt[2].x; */
  }

  XFillPolygon(dpy, bitmap, bgc, pt, 3, Convex, CoordModeOrigin);
  XFillPolygon(dpy, pix, gc, ipt, 3, Convex, CoordModeOrigin);

  /* fix outline */
  XDrawLines(dpy, pix, WMColorGC(black), pt, 3, CoordModeOrigin);
  if (side & RIGHT) {
    pt[0].x++;
    pt[2].x--;
  } else {
    pt[0].x--;
    pt[2].x++;
  }
  XDrawLines(dpy, pix, WMColorGC(black), pt, 3, CoordModeOrigin);

  WMReleaseColor(white);
  WMReleaseColor(black);
}

static Pixmap makePixmap(WMScreen *scr, int width, int height, int side, Pixmap *mask)
{
  Display *dpy = WMScreenDisplay(scr);
  Pixmap bitmap;
  Pixmap pixmap;
  int x, y;
  WMColor *black = WMBlackColor(scr);

  bitmap = XCreatePixmap(dpy, scr->rootWin, width + SPACE, height + SPACE, 1);

  XSetForeground(dpy, scr->monoGC, 0);
  XFillRectangle(dpy, bitmap, scr->monoGC, 0, 0, width + SPACE, height + SPACE);

  pixmap = XCreatePixmap(dpy, scr->rootWin, width + SPACE, height + SPACE, scr->depth);

  XFillRectangle(dpy, pixmap, WMColorGC(black), 0, 0, width + SPACE, height + SPACE);

  if (side & BOTTOM) {
    y = 0;
  } else {
    y = SPACE;
  }
  x = 0;

  drawBalloon(scr, bitmap, pixmap, x, y, width, height, side);

  *mask = bitmap;

  WMReleaseColor(black);

  return pixmap;
}

static void showText(Balloon *bPtr, int x, int y, int w, int h, const char *text)
{
  WMScreen *scr = bPtr->view->screen;
  Display *dpy = WMScreenDisplay(scr);
  int width;
  int height;
  Pixmap pixmap;
  Pixmap mask;
  WMFont *font = bPtr->font ? bPtr->font : scr->normalFont;
  int textHeight;
  int side = 0;
  int ty;
  int bx, by;

  {
    int w;
    const char *ptr, *ptr2;

    ptr2 = ptr = text;
    width = 0;
    while (ptr && ptr2) {
      ptr2 = strchr(ptr, '\n');
      if (ptr2) {
        w = WMWidthOfString(font, ptr, ptr2 - ptr);
      } else {
        w = WMWidthOfString(font, ptr, strlen(ptr));
      }
      if (w > width)
        width = w;
      ptr = ptr2 + 1;
    }
  }

  width += 16;

  textHeight = WMGetTextHeight(font, text, width, False);

  height = textHeight + 4;

  if (height < 16)
    height = 16;
  if (width < height)
    width = height;

  if (x + width > scr->rootView->size.width) {
    side = RIGHT;
    bx = x - width + w / 2;
    if (bx < 0)
      bx = 0;
  } else {
    side = LEFT;
    bx = x + w / 2;
  }
  if (bx + width > scr->rootView->size.width)
    bx = scr->rootView->size.width - width;

  if (y - (height + SPACE) < 0) {
    side |= TOP;
    by = y + h - 1;
    ty = SPACE;
  } else {
    side |= BOTTOM;
    by = y - (height + SPACE);
    ty = 0;
  }
  pixmap = makePixmap(scr, width, height, side, &mask);

  WMPaintText(bPtr->view, pixmap, font, 8, ty + (height - textHeight) / 2,
              width, bPtr->flags.alignment,
              bPtr->textColor ? bPtr->textColor : scr->black, False, text, strlen(text));

  XSetWindowBackgroundPixmap(dpy, bPtr->view->window, pixmap);

  WMResizeView(bPtr->view, width, height + SPACE);

  XFreePixmap(dpy, pixmap);

#ifdef USE_XSHAPE
  XShapeCombineMask(dpy, bPtr->view->window, ShapeBounding, 0, 0, mask, ShapeSet);
#endif
  XFreePixmap(dpy, mask);

  WMMoveView(bPtr->view, bx, by);

  WMMapView(bPtr->view);
}

static void handleEvents(XEvent *event, void *data)
{
  Balloon *bPtr = (Balloon *) data;

  switch (event->type) {
  case DestroyNotify:
    destroyBalloon(bPtr);
    break;
  }
}

static void destroyBalloon(Balloon *bPtr)
{
  WMHashEnumerator e;
  char *str;

  e = WMEnumerateHashTable(bPtr->table);

  while ((str = WMNextHashEnumeratorItem(&e))) {
    wfree(str);
  }
  WMFreeHashTable(bPtr->table);

  if (bPtr->textColor)
    WMReleaseColor(bPtr->textColor);

  if (bPtr->font)
    WMReleaseFont(bPtr->font);

  wfree(bPtr);
}
